`include "float_arith_pkg.svh"
`include "float_sim_pkg.svh"

import float_arith::*;
import float_sim::*;

class Float32Case;
   rand fp_cfg_t fp_cfg;
   rand float32_bit_t a, b;

   rand bit is_sub;
   // +-0
   // subnormal
   // normal
   // +-INF
   // sNaN/qNaN

   
   constraint c {
      // a.exp == 8'hff;
      // b.exp == 8'hff;

      // a.sig == b.sig;

      // a.sig dist { 0:/10, [1:23'h7f_ffff]:/90 };
      // b.sig dist { 0:/10, [1:23'h7f_ffff]:/90 };


      // a.sig == b.sig;
      // a.sig == '0;

      // a.exp == b.exp;		// INF
      // a.exp == 8'hff;
      
      b.exp == '1;		// b is INF 
      b.sig == '0;

		 
		 
      
      // a.sig == '0;
      // b.sig == '0;

      // a.sign == b.sign;
      // a.sign == 0;		// but underflow ???
      // b.sign == 0;

      // a.sign == 0;
      // b.sign == 1;
      // a.sign != b.sign;


      // a.sign == 0;
      // b.sign == 1;
      // a.exp == 8'h98;
      // b.exp == 8'h7f;
      // a.sig == 0;
      // b.sig == 0;

      // is_sub == 1;
		 };
endclass // Float32Case


// import "DPI-C" function void float32_add_ref( output float32_t sum,
// 					      output excp_t excp,
// 					      input float32_t a, 
// 					      input float32_t b);


typedef int unsigned cfloat32;
import "DPI-C" float32_add = function cfloat32 c_float32_add(input cfloat32 a, b);
   import "DPI-C" float32_sub = function cfloat32 c_float32_sub(input cfloat32 a, b);
      import "DPI-C" float32_mul = function cfloat32 c_float32_mul(input cfloat32 a, b);
	 import "DPI-C" float32_muladd = function cfloat32 c_float32_muladd(input cfloat32 a, b, c);

	 // deal exceptions
	 import "DPI-C" get_exception = function int c_get_exception();
	    import "DPI-C" clear_exception = function void c_clear_exception();
	       import "DPI-C" set_tininess_detect = function void c_set_tininess_detect(input int mode);
		  import "DPI-C" set_rounding_mode = function void c_set_rounding_mode(input int rounding_mode);
	       

   function automatic excp_t get_excp();
	    int excp_flags;
	    excp_t excp;
	    
	    excp_flags = c_get_exception();
	    
	    excp.invalid = excp_flags>>0;
	    excp.divbyzero = excp_flags>>2;
	    excp.overflow = excp_flags>>3;
	    excp.underflow = excp_flags>>4;
	    excp.inexact = excp_flags>>5;
	    return excp;
	    
	 endfunction // get_excp
	 
	 function automatic void clear_excp();
	    c_clear_exception();
	 endfunction // clear_excp
	 
	 
	 function automatic void float32_addsub_ref(output float32_t sum,
						 output excp_t excp,
						 input fp_cfg_t fp_cfg,
						 input float32_t a, b,
						 input bit is_sub);
	    cfloat32 ca, cb;
	    ca = a;
	    cb = b;
	    
	    c_set_rounding_mode(fp_cfg.rounding_mode);
	    c_set_tininess_detect(fp_cfg.tininess_detect);
	    clear_excp();
	    if(!is_sub)
	      sum = c_float32_add(ca, cb);
	    else
	      sum = c_float32_sub(ca, cb);
	    excp = get_excp();
	 endfunction



	 function automatic void float32_mul_ref(output float32_t prod,
						 output excp_t excp,
						 input fp_cfg_t fp_cfg,
						 input float32_t a, b);
	    cfloat32 ca, cb;
	    ca = a;
	    cb = b;
	    
	    c_set_rounding_mode(fp_cfg.rounding_mode);
	    c_set_tininess_detect(fp_cfg.tininess_detect);
	    clear_excp();
	    prod = c_float32_mul(ca, cb);
	    excp = get_excp();
	 endfunction


		     // fused mul-add
	 function automatic void float32_fma_ref(output float32_t prod,
						 output excp_t excp,
						 input fp_cfg_t fp_cfg,
						 input float32_t a, b, c);
	    cfloat32 ca, cb, cc;
	    ca = a;
	    cb = b;
	    cc = c;
	    
	    c_set_rounding_mode(fp_cfg.rounding_mode);
	    c_set_tininess_detect(fp_cfg.tininess_detect);
	    clear_excp();
	    prod = c_float32_muladd(ca, cb, cc);
	    excp = get_excp();
	 endfunction
	 

module tb;


   bit clk;
   always #5 clk = ~clk;
   
   
   float32_t correct_result;
   excp_t correct_excp;
	
   float32_t model_result;
   excp_t model_excp;

   
   // inputs
   fp_cfg_t fp_cfg;
   float32_t a, b, c;
   float32_t result;
   excp_t excp;

   bit is_mul_neg;
   bit is_c_neg;
   

   float32_fma u_float32_fma(/*autoinst*/
			     // Interfaces
			     .o_result		(result),
			     .o_excp		(excp),
			     .fp_cfg		(fp_cfg),
			     .a			(a),
			     .b			(b),
			     .c			(c),
			     .is_mul_neg	(is_mul_neg),
			     .is_c_neg		(is_c_neg) );

   

   initial
     begin
	// for debug
	float32_mantissa_t model_msml, model_mbig;
	bit [1:0] model_roundbits;
	bit 	  debug;
	  
	// generator
	FpGen fg;
	mailbox mbox;
	FpCase fc;

	int 	err_cnt;
	
	// run generator
	// mbox = new(10);
	mbox = new(10);	// for debug
	fg = new(mbox);
	fork
	   fg.start();
	join_none


	#1;			// wait fpgen to gen case

	err_cnt = 0;
	
	$display();
	forever
	  begin
	     bit err_flag;
	     float32_t ta, tb, tc;

	     // assert(fc.randomize());
	     // mbox.get(fc);
	     if(mbox.try_get(fc) <= 0)
	       begin
		  $display("can't get any cases, @ ", $time);
		  $finish;
	       end

	     // fc.a = 32'h299a835f;
	     // fc.b = 32'h131fdc36;
	     // fc.c = {1'b0, 8'b0, 23'h7e7e0d};

	     {is_mul_neg, is_c_neg} = fc.insn;
	     
	     ta = {fc.a.sign ^ is_mul_neg, fc.a.exp, fc.a.sig};
	     tb = fc.b;
	     tc = {fc.c.sign ^ is_c_neg, fc.c.exp, fc.c.sig};


	     float32_fma_ref(correct_result, correct_excp, fc.fp_cfg, ta, tb, tc);

	     fp_cfg = fc.fp_cfg;
	     a = fc.a;
	     b = fc.b;
	     c = fc.c;
	     // {is_mul_neg, is_c_neg} = fc.insn;
	     // float32_fma_new(result, excp, fc.fp_cfg, fc.a, fc.b, fc.c, debug);

	     
	     // if(!debug)		// test the branch which prod need shift right
	       // continue;

	     // if(!debug)
	     //   begin
	     // 	  @(posedge clk);
	     // 	  continue;
	     //   end
	     

	     @(posedge clk);


	     // if(tb.u_float32_fma.debug)		// test the branch which prod need shift right
	       // continue;
	     
	     
 	     // if(result === correct_result && excp === correct_excp)
	     if( result === correct_result && 
		 excp.inexact === correct_excp.inexact &&
		 excp.overflow === correct_excp.overflow &&
		 excp.underflow === correct_excp.underflow
		)
	     // if(result[22:0] === correct_result[22:0])
	       begin
		  err_flag = 0;
		  // if(tb.u_faddsub.need_lzd === 0)
		  //   if(tb.u_faddsub.sum.sig !== model_result.sig)
		  //     err_flag = 1;
	       end
	     else
	       err_flag = 1;

	     // if(u_float32_fma.subn_op_subn)
	     //   err_flag = 1;

	     // $display("diff_base_borrow = %d", u_float32_fma.diff_base_borrow);
	     // if(u_float32_fma.prod_sft[48])
	     //   begin
	     // 	  err_flag = 1;
	     //   end
	     
	     if(err_flag == 0)
	       continue;


	     $display("==================================================");
	     $display("%s : %s @%t", fc.insn.name(), (err_flag ? "ERROR   " : "SUCCEESS  "), $time);
	     $display("rounding_mode:%s(%b), tininess:%s rounding", 
		      roundmode2str(fc.fp_cfg.rounding_mode), fc.fp_cfg.rounding_mode, 
		      fc.fp_cfg.tininess_detect ? "before" : "after");
	     $display("op_a   = %x (%s, %s)", fc.a, float32_to_fieldstr(fc.a), float32_to_str(fc.a));
	     $display("op_b   = %x (%s, %s)", fc.b, float32_to_fieldstr(fc.b), float32_to_str(fc.b));
	     $display("op_c   = %x (%s, %s)", fc.c, float32_to_fieldstr(fc.c), float32_to_str(fc.c));
	     $display("correct= %x (%s, %s)", correct_result, float32_to_fieldstr(correct_result), float32_to_str(correct_result));
	     // $display("model  = %x (%s, %s)", model_result, float32_to_fieldstr(model_result), float32_to_str(model_result));
	     $display("result = %x (%s, %s)", result, float32_to_fieldstr(result), float32_to_str(result));
	     $display("cor_exp= %b (%s)", correct_excp, exception2str(correct_excp));
	     // $display("mdl_exp= %b (%s)", model_excp, exception2str(model_excp));
	     $display("excp   = %b (%s)", excp, exception2str(excp));

	     begin
		bit [23:0] frac_a, frac_b;
		bit [47:0] prod;
		frac_a = {|a.exp, a.sig};
		frac_b = {|b.exp, b.sig};
		prod = frac_a * frac_b;

		$display("--------------------------------------------------");
		$display("prod_sft   = %012x", u_float32_fma.prod_sft[48:1]);
		$display("frac_c_sft = %012x", u_float32_fma.frac_c_sft[48:1]);
		if(u_float32_fma.is_sub)
		  $display("diff = %012x", u_float32_fma.diff[48:1]);
		else
		  $display("sum = %012x", u_float32_fma.diff[48:1]);
		$display("--------------------------------------------------");
		$display("exp = %02x", u_float32_fma.expn);
		$display("--------------------------------------------------");

		$display("diff_ext = %x",u_float32_fma.diff);

		$display("prod_sft_ext  = %013x", u_float32_fma.prod_sft);
		$display("frac_c_sft_ext= %013x", u_float32_fma.frac_c_sft);

		$display("expn=%x,dif_lzn=%x", u_float32_fma.expn, u_float32_fma.diff_lzn);

	     end
	     
	     
	     // if(++err_cnt < 20)
	     if(++err_cnt < 5)
	       continue;
	       
	     
	     @(posedge clk);
	     // float32_fma_new(result, excp, fc.fp_cfg, fc.a, fc.b, fc.c, debug);	
	     $finish;

	     
	  end // forever begin
	

	$display("finish ...");
	$finish;
	     
     end // initial begin

endmodule // tb
